﻿/// -----------------------------------------------------------------------
/// (c) 2009 Microsoft Corporation -- All rights reserved
/// This code is licensed under the MS-PL
/// http://www.opensource.org/licenses/ms-pl.html
/// Courtesy of the Open Source Techology Center: http://port25.technet.com
/// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using gsToolkit;
using Microsoft.VisualStudio.VCProjectEngine;
using System.IO;

namespace mkProject {
    public class Target {
        // outputs
        private List<string> oDllFiles;
        private List<string> oLibFiles;
        private List<string> oExeFiles;

        // intermediate inputs
        public List<string> iObjFiles;
        public List<string> iLibFiles;
        public List<string> iResFiles;

        // source inputs
        private List<string> sCFiles;
        private List<string> sRcFiles;
        public List<string> sHFiles;

        private List<XmlNode> sourceProcs;

        public string DependList {
             get {
                 StringBuilder sb = new StringBuilder();
                 string dependSection = "\tProjectSection(ProjectDependencies) = postProject\r\n{0}\tEndProjectSection";

                 List<string> deps = App.addDependencies.Lookup(ProjectName);
                 foreach(var dep in deps) {
                     Guid prjGuid = Guid.Empty;
                     try {
                         prjGuid = new Guid(dep);
                     }
                     catch {
                     }
                     if(prjGuid != Guid.Empty) {
                         sb.Append("\t\t{");
                         sb.Append(prjGuid.ToString("D").ToUpper());
                         sb.Append("} = {");
                         sb.Append(prjGuid.ToString("D").ToUpper());
                         sb.AppendLine("}");
                     }
                     else {
                         foreach(Target t in App.OriginalTargets) {
                             if (t.ProjectName.Equals(dep, StringComparison.InvariantCultureIgnoreCase)) {
                                 sb.Append("\t\t{");
                                 sb.Append(t.VCProject.ProjectGUID.ToUpper());
                                 sb.Append("} = {");
                                 sb.Append(t.VCProject.ProjectGUID.ToUpper());
                                 sb.AppendLine("}");
                             }
                         }
                     }
                 }
                 return sb.Length > 0 ? dependSection.format(sb.ToString()) : "";
             }
        }

        private string pid;

        public List<string> postBuildScript = new List<string>();
        public List<string> preLinkScript = new List<string>();
        public List<string> preBuildScript = new List<string>();

        public string outputExtension = "";
        private static MakeProject App = MakeProject.App;

        private string projectFileName;
        public string ProjectName;
        public VCProject VCProject;
        public TargetCommandLine TargetCommand;
        public List<SourceFile> sourceFiles = new List<SourceFile>();
        public List<string> additionalLinkerInputs = new List<string>();

        public Target(XmlNode originalProc) {
            pid = originalProc.Attributes["pid"].Value;

            oDllFiles = GetFiles(originalProc, false, ".dll");
            oLibFiles = GetFiles(originalProc, false, ".lib");
            oExeFiles = GetFiles(originalProc, false, ".exe");

            if(oDllFiles.Count == 1 &&  oLibFiles.Count == 1 && oExeFiles.Count == 0)
                outputExtension = ".dll";

            else if(oDllFiles.Count == 0 &&  oLibFiles.Count == 1 && oExeFiles.Count == 0)
                outputExtension = ".lib";

            else if(oDllFiles.Count == 1 && oLibFiles.Count == 0 && oExeFiles.Count == 0)
                outputExtension = ".dll";

            else if(oExeFiles.Count == 1 && oLibFiles.Count == 0 && oDllFiles.Count == 0)
                outputExtension = ".exe";

            else {
                throw new Exception("Something's not right. Can't figure out the ouptut file?[{0},{1},{2}][PID={3}]".format( oExeFiles.Count, oLibFiles.Count, oDllFiles.Count, pid));
            }

            TargetCommand = new LinkerCommandLine(originalProc.XPath("Line")[0].InnerText);

            ProjectName = App.projectNameAliases.Lookup(TargetCommand.UniqueName);
            string tmp = App.outputFilesPath.Lookup(ProjectName);
            if(tmp.Equals(ProjectName))
                tmp = "";
            projectFileName = Path.Combine(tmp, ProjectName + ".vcproj");
            GetVCProject();


            iObjFiles= GetFiles(originalProc, true, ".obj", MakeProject.App.ProjectRoot);
            iLibFiles = GetFiles(originalProc, true, ".lib", MakeProject.App.ProjectRoot);
            iResFiles = GetFiles(originalProc, true, ".res", MakeProject.App.ProjectRoot);

            sCFiles = new List<string>();
            sCFiles.AddRange(GetFiles(originalProc, true, ".c", MakeProject.App.ProjectRoot));
            sCFiles.AddRange(GetFiles(originalProc, true, ".cpp", MakeProject.App.ProjectRoot));
            sCFiles.AddRange(GetFiles(originalProc, true, ".cxx", MakeProject.App.ProjectRoot));


            // don't think this ever happens tho'
            sRcFiles = GetFiles(originalProc, true, ".rc", MakeProject.App.ProjectRoot);

            sourceProcs = new List<XmlNode>();
            // for each of the intermediate files, find the preceding process that created it.
            foreach( string objFile in iObjFiles) {
                XmlNodeList x = originalProc.XPath("//Process[@pid={0}]/preceding::Process//file[@write='true' and . = '{1}']/ancestor::Process[last()]", pid, objFile);
                if( x.Count == 0 ) {
                    MakeProject.App.Fail("Unable to find match for intermediate:[{0}]", objFile);
                }
                else {
                    // only one node here
                    if(! sourceProcs.Contains(x[0]))
                        sourceProcs.Add(  x[0] );
                }
            }

            foreach(string objFile in iResFiles) {
                XmlNodeList x = originalProc.XPath("//Process[@pid={0}]/preceding::Process//file[@write='true' and . = '{1}']/ancestor::Process[last()]", pid, objFile);
                if(x.Count == 0) {
                    MakeProject.App.Fail("Unable to find match for intermediate:[{0}]", objFile);
                }
                else {
                    // only one node here
                    if(!sourceProcs.Contains(x[0]))
                        sourceProcs.Add(x[0]);
                }
            }

            if(sCFiles.Count >0)
                sourceProcs.Add(originalProc);

            sHFiles = new List<string>();

            foreach(XmlNode sourceProc in sourceProcs) {
                CompileCommandLine ccl = new CompileCommandLine(sourceProc.XPath("Line")[0].InnerText);

                // This doesn't work:
                // there are cases where someone #includes a c/cpp/cxx file.
                /*
                List<string> cFiles = new List<string>();
                List<string> rcFiles = new List<string>();

                cFiles.AddRange(GetFiles(sourceProc, true, ".c", MakeProject.App.ProjectRoot));
                cFiles.AddRange(GetFiles(sourceProc, true, ".cpp", MakeProject.App.ProjectRoot));
                cFiles.AddRange(GetFiles(sourceProc, true, ".cxx", MakeProject.App.ProjectRoot));

                rcFiles.AddRange(GetFiles(sourceProc, true, ".rc", MakeProject.App.ProjectRoot));

                foreach( string cFile in cFiles ) {
                    sourceFiles.Add(new SourceFile() { fileName = cFile, includes = ccl.includes,defines = ccl.defines });
                }
                */

                sourceFiles.AddRange(ccl.sourceFiles);

                sHFiles.AddRange(GetFiles(sourceProc, true, ".h", MakeProject.App.ProjectRoot));
                sHFiles.AddRange(GetFiles(sourceProc, true, ".hpp", MakeProject.App.ProjectRoot));
                sHFiles.AddRange(GetFiles(sourceProc, true, ".hxx", MakeProject.App.ProjectRoot));
            }

            GetCommonDefines();
            GetCommonIncludes();

            List<string> removeLibs = App.ignoreLibraries.Lookup(ProjectName);
            for( int i = TargetCommand.libFiles.Count-1; i>=0; i--) {
                foreach(string lib in removeLibs)
                    if(TargetCommand.libFiles[i].Equals(lib, StringComparison.CurrentCultureIgnoreCase)) {
                        TargetCommand.libFiles.RemoveAt(i);
                        break;
                    }

            }
            TargetCommand.libFiles.AddRange( App.addLibraries.Lookup(ProjectName) );
            TargetCommand.libFiles.Uniq();
        }

        private void GetCommonIncludes() {
            CommonIncludes.AddRange(sourceFiles[0].includes);
            foreach(SourceFile cl in sourceFiles) {
                List<string> removeList = new List<string>();
                foreach(string commonInclude in CommonIncludes) {
                    bool isGlobal = false;
                    foreach(string include in cl.includes) {
                        if(commonInclude.Equals(include))
                            isGlobal = true;
                    }
                    if(!isGlobal)
                        removeList.Add(commonInclude);
                }
                foreach(string s in removeList)
                    CommonIncludes.Remove(s);
            }
        }

        private void GetCommonDefines() {
            CommonDefines.AddRange(sourceFiles[0].defines);
            foreach(SourceFile cl in sourceFiles) {
                List<string> removeList = new List<string>();
                foreach(string commonDefine in CommonDefines) {
                    bool isGlobal = false;
                    foreach(string define in cl.defines) {
                        if(commonDefine.Equals(define))
                            isGlobal = true;
                    }
                    if(!isGlobal)
                        removeList.Add(commonDefine);
                }
                foreach(string s in removeList)
                    CommonDefines.Remove(s);
            }
        }

        public List<string> CommonDefines = new List<string>();
        public List<string> CommonIncludes = new List<string>();


        private static List<string> GetFiles(XmlNode topNode , bool read, string extension ) {
            return topNode.XPath(".//file[@{0} = 'true' and substring(.,string-length(.)-{2},{3})='{1}']", read?"read":"write", extension, extension.Length-1, extension.Length).InnerText();

        }

        private static List<string> GetFiles(XmlNode topNode, bool read, string extension, string rootDir) {
            return topNode.XPath(".//file[@{0} = 'true' and starts-with(., '{4}' ) and substring(.,string-length(.)-{2},{3})='{1}']", read?"read":"write", extension, extension.Length-1, extension.Length, rootDir).InnerText();

        }

        private void GetVCProject() {
            if((!App.recreate) && File.Exists(projectFileName)) {
                Console.WriteLine("Updating Project File: [{0}]", projectFileName);
                if( !App.preservePropertySheets ) {
                    string prjFileText = File.ReadAllText(projectFileName);
                    prjFileText = Regex.Replace(prjFileText, "InheritedPropertySheets=\".*\"", "");
                    string tmpFilename = Guid.NewGuid().ToString("D") + ".vcproj";
                    File.WriteAllText( tmpFilename, prjFileText);
                    VCProject = (VCProject)App.VCProjectEngine.LoadProject(tmpFilename);
                    File.Delete(tmpFilename);
                    VCProject.ProjectFile = projectFileName;
                }
                else {
                    VCProject = (VCProject)App.VCProjectEngine.LoadProject(projectFileName);
                }
                return;
            }

            VCProject = (VCProject)App.VCProjectEngine.LoadProject(Path.Combine(App.projectTemplateFilesPath.Lookup(ProjectName), string.Format(@"Template{0}.vcproj", TargetCommand.OutputExtension)));
            VCProject.ProjectFile = projectFileName;
            VCProject.ProjectGUID = Guid.NewGuid().ToString("D");
            VCProject.Name = ProjectName;
            Console.WriteLine("Creating Project File: [{0}]", projectFileName);
        }

        public void ProcessMiscLines() {
            var linesUnhandled = 0;

            foreach(XmlNode n in App.TraceXmlDocument.XPath("//Process/Line")) {
                var l = n.InnerText;
                var handled = false;
                List<string> set;

                if(null != (set = App.ignore_commands.Lookup(ProjectName))) {
                    // already handled these:
                    set.AddRange(new[] { "cl.exe", "rc.exe", "mt.exe", "lib.exe", "link.exe" });

                    // and ignore everything handled elsewhere.
                    set.AddRange(App.post_build_commands.LookupReverse(ProjectName));
                    set.AddRange(App.pre_build_commands.LookupReverse(ProjectName));
                    set.AddRange(App.pre_link_commands.LookupReverse(ProjectName));

                    foreach(string rx in set) {
                        if(l.MatchIgnoreCase(rx).Success) {
                            handled = true;
                            // "Ignored Line:    {0}".Print(l);
                            break;
                        }
                    }
                }
                if(handled)
                    continue;

                if(null != (set = App.post_build_commands.Lookup(ProjectName)))
                    foreach(string rx in set) {
                        if(l.MatchIgnoreCase(rx).Success) {
                            handled = true;
                            // "PostBuild Line:    {0}".Print(l);
                            postBuildScript.Add(l);
                            break;
                        }
                    }

                if(handled)
                    continue;

                if(null != (set = App.pre_link_commands.Lookup(ProjectName)))
                    foreach(string rx in set) {
                        if(l.MatchIgnoreCase(rx).Success) {
                            handled = true;
                            // "Pre Link Line:    {0}".Print(l);
                            preLinkScript.Add(l);
                            break;
                        }
                    }

                if(handled)
                    continue;

                if(null != (set = App.pre_build_commands.Lookup(ProjectName)))
                    foreach(string rx in set) {
                        if(l.MatchIgnoreCase(rx).Success) {
                            handled = true;
                            // "Pre Build Line:    {0}".Print(l);
                            preBuildScript.Add(l);
                            break;
                        }
                    }
                if(handled)
                    continue;

                linesUnhandled++;
                using(new ConsoleColors(ConsoleColor.Magenta, ConsoleColor.Black))
                    "Unhandled Line:    {0}".Print(n.InnerText);
            }

            List<string> cmds;
            if(null != (cmds = App.add_pre_build_commands.Lookup(ProjectName)))
                foreach(string cmd in cmds) {
                    preBuildScript.Add(cmd);
                }
            if(null != (cmds = App.add_post_build_commands.Lookup(ProjectName)))
                foreach(string cmd in cmds) {
                    postBuildScript.Add(cmd);
                }
            if(null != (cmds = App.add_pre_link_commands.Lookup(ProjectName)))
                foreach(string cmd in cmds) {
                    preLinkScript.Add(cmd);
                }

            if(linesUnhandled > 0) {
                App.Fail("There are {0} executuion lines in the log that are not handled.", linesUnhandled);
            }

        }
    }
}
